import json
import math

"""
在正常情况下我们计算方差、平均值这些数据都需要有全量的数据来支撑计算
但如果数据条数达到亿级或超出缓存限制的时候，这很明显是不可行的，一是效率低二是会oom宕机

所以我们采取分布式/增量的计算方式来计算平均值和方差，这样可以避免oom的同时，提升程序效率

ex: 假设6w条数据，均衡打散至200个线程资源，
    即200组数据(300条/组)，局部计算完毕后进行增量计算，直至200组数据计算完成且增量结束

* 全量 *
    平均值 = (x1 + x2 + ... + xn) / n
    方差 = ((x1 - avg)² + (x2 - avg)² + ... (xn - avg)²) / n
* 增量 *
    平均值 = (avg1 + avg2) / n1 + n2 (= new_avg)
    方差 = (n1(var1 + (new_avg - avg1)²) + n2(var2 + (new_avg - avg2)²)) / n1 + n2
"""


def init_obj(arr):
    """
    计算当前数组的个数、总和、均值和方差
    * 类似于局部/分布式中其中一组计算
    :param arr:
    :return:
    """
    cnt_data = len(arr)
    sum_data = sum(arr)
    avg_data = sum_data / cnt_data
    variance = 0
    for a in arr:
        variance += math.pow(a - avg_data, 2)
    variance_data = variance / cnt_data

    return AggObj(cnt_data, sum_data, avg_data, variance_data)


class AggObj:
    """
    这是增量计算的数据载体类
    """

    def __init__(self, count, summary, average, variance):
        """
        初始化方法
        :param count:
        :param summary:
        :param average:
        :param variance:
        """
        self.count = count
        self.summary = summary
        self.average = average
        self.variance = variance

    def to_json(self):
        """
        对象转为json
        :return: json_str
        """
        dict_json = {
            'cnt': self.count,
            'sum': self.summary,
            'avg': self.average,
            'var': self.variance
        }
        return json.dumps(dict_json)

    def evolve(self, obj):
        """
        两两增量进化合体为一个新对象
        :param obj:
        :return:
        """
        new_cnt = self.count + obj.count
        new_sum = self.summary + obj.summary
        new_avg = new_sum / new_cnt

        new_var_part1 = self.count * (self.variance + math.pow(new_avg - self.average, 2))
        new_var_part2 = obj.count * (obj.variance + math.pow(new_avg - obj.average, 2))

        new_var = (new_var_part1 + new_var_part2) / new_cnt
        return AggObj(new_cnt, new_sum, new_avg, new_var)


'''test'''
d1 = init_obj([0.1, 0.2, 0.12, 0.1, 1])
d2 = init_obj([0.2, 0.2, 0.11, 0.2, 0.092])
d3 = d1.evolve(d2)
print(d3.to_json())

d4 = init_obj([0.1, 0.2, 0.12, 0.1, 1, 0.2, 0.2, 0.11, 0.2, 0.092])
print(d4.to_json())
